#include <iostream>
#include <fstream>
#include "ULCore_BinFile.h"

//Don't save repeat* members in class ULC_Serial to file
#define ULC_SERIAL_IO_SIZE	(sizeof(ULC_Serial) - 8)

static void SerialToBinSerial(ULC_BinFileSerial *binSr, ULC_Serial *serial)
{
	memcpy(binSr, serial, ULC_SERIAL_IO_SIZE);
	binSr->sn = serial->sn;
	strncpy(binSr->date, serial->date, DATE_ALIGN_SIZE);
	
	for (int i = 0; i < UL_RED_BALLS_COUNT; i++){
		binSr->sortedBalls[i] = serial->sortedBalls.reds[i];
	}

	binSr->sortedBalls[UL_RED_BALLS_COUNT] = serial->sortedBalls.blue;

	for (int i = 0; i < UL_RED_BALLS_COUNT; i++){
		binSr->actualOutBalls[i] = serial->actualOutBalls.reds[i];
	}
	
	memcpy(binSr->extraData, serial->extraData, sizeof(int) * EXTRA_DATA_SIZE);
	binSr->top = serial->top;
	binSr->second = serial->second;
	binSr->third = serial->third;
	binSr->fourth = serial->fourth;
	binSr->fifth = serial->fifth;
	binSr->sixth = serial->sixth;
	
}

static void BinSerialToSerial(ULC_Serial *serial, ULC_BinFileSerial *binSr)
{
	serial->sn = binSr->sn;
	strncpy(serial->date, binSr->date, DATE_ALIGN_SIZE);
	serial->sortedBalls.SetNumbers(binSr->sortedBalls, binSr->sortedBalls[UL_RED_BALLS_COUNT]);
	serial->actualOutBalls.SetNumbers(binSr->actualOutBalls, 0);
	memcpy(serial->extraData, binSr->extraData, sizeof(int) * EXTRA_DATA_SIZE);
	serial->top = binSr->top;
	serial->second = binSr->second;
	serial->third = binSr->third;
	serial->fourth = binSr->fourth;
	serial->fifth = binSr->fifth;
	serial->sixth = binSr->sixth;
	
	//Temp debug
	//cout << "[---- binSr->sn " << binSr->sn << " serial at " << serial << endl;
}

static void BinFileHeaderDebug(ULC_BinFileHeader *header)
{
 	   cout << "+++++++++++++++++++++++++++++++++++++++++" << endl;
 	   cout << "Bin File Header: " << endl;
 	   cout << "Total serials: " << header->totalSerials << endl << "Latest serial: " << header->latestSerial << endl;
}

int ULC_BinFile::SaveSerials(const char *fileName, int totalSerials, ULC_Serial *serials)
{
	ofstream outf;
	ULC_BinFileHeader header;
	ULC_BinFileSerial binSerial;
	char *buf;

	if (fileName == NULL || totalSerials <= 0 || serials == NULL){
		return -1;
	}	

	outf.open(fileName, ios::out | ios::binary | ios::trunc);

	if (outf.fail()){
#ifdef ULC_BIN_FILE_DEBUG
		cout << __func__ << "Cannot open " << fileName << endl;
#endif
		return -1;
	}
	
	//Write header
	buf = (char *)&header;
	
	header.totalSerials = totalSerials;
	header.latestSerial = serials[totalSerials - 1].sn;
#ifdef ULC_BIN_FILE_DEBUG
	   cout << "ULC_BinFile -> " << __func__ << endl;
	   BinFileHeaderDebug(&header);
#endif
	outf.write(buf, sizeof(header));
	//Write serials
	buf = (char *)&binSerial;

	for (int i = 0; i < totalSerials; i++){
		SerialToBinSerial(&binSerial, serials + i);
		outf.write(buf, sizeof(binSerial));
	}

	outf.close();
	return 0;
}

int ULC_BinFile::LoadSerials(const char *fileName, ULC_Serial *serials)
{
	ifstream inf;
	char *buf;
	int totalSerials;
	ULC_BinFileHeader header;
	ULC_BinFileSerial binSerial;

	if (fileName == NULL || serials == NULL){
		return -1;
	}

	inf.open(fileName, ios::in | ios::binary);

	if (inf.fail()){
		return -1;
	}

	//Read header
	buf = (char *)&header;

	inf.read(buf, sizeof(header));

	totalSerials = header.totalSerials;

#ifdef ULC_BIN_FILE_DEBUG
	   cout << "        ULC_BinFile -> " << __func__ << endl;
	   BinFileHeaderDebug(&header);
#endif
	if (totalSerials <= 0){
		//File format is wrong or file is corrupted
		inf.close();
		return -1;
	}
	
	buf = (char *)(&binSerial);

	for (int i = 0; i < totalSerials; i++){
		inf.read(buf, sizeof(binSerial));	
		BinSerialToSerial(serials + i, &binSerial);

#ifdef ULC_BIN_FILE_DEBUG				
		if (i % 500 == 0){
  		   	  cout << *(serials + i) << endl;
  		}
#endif
	}

	inf.close();
	return 0;
}

int ULC_BinFile::GetTotalSerials(const char *fileName)
{
	ifstream inf;
	ULC_BinFileHeader header;
	char *buf;

	if (fileName == NULL){
 #ifdef ULC_BIN_FILE_DEBUG
 		cout << "ULC_BinFile -> " << __func__ << ": fileName is NULL" << endl;
 #endif
		return 0;
	}

	inf.open(fileName);

	if (inf.fail()){
 #ifdef ULC_BIN_FILE_DEBUG
 		cout << "ULC_BinFile -> " << __func__ << ": cannot open file" << fileName << endl;
 #endif
		return 0;
	}
	
	buf = (char *)&header;
	inf.read(buf, sizeof(header));

#ifdef ULC_BIN_FILE_DEBUG
	   cout << "ULC_BinFile -> " << __func__ << endl;
	   BinFileHeaderDebug(&header);
#endif

	inf.close();
	return header.totalSerials;
}



int ULC_BinFile::GetLatestSerial(const char *fileName)
{
	ifstream inf;
	ULC_BinFileHeader header;
	char *buf;

	if (fileName == NULL){
 #ifdef ULC_BIN_FILE_DEBUG
 		cout << "ULC_BinFile -> " << __func__ << ": fileName is NULL" << endl;
 #endif
		return 0;
	}

	inf.open(fileName, ios::in | ios::binary);

	if (inf.fail()){
 #ifdef ULC_BIN_FILE_DEBUG
 		cout << "ULC_BinFile -> " << __func__ << ": inf.open() failed" << endl;
 #endif
		return 0;
	}
	
	buf = (char *)&header;
	inf.read(buf, sizeof(header));

#ifdef ULC_BIN_FILE_DEBUG
	   BinFileHeaderDebug(&header);
#endif

	inf.close();
	return header.latestSerial;
}

static void UpdateHeader(const char *fileName, ULC_BinFileHeader header)
{
	fstream outf;
	char *buf = (char *)&header;

	outf.open(fileName, ios::in | ios::out | ios::binary);

	if (outf.fail()){
		return ;
	}

	outf.write(buf, sizeof(header));
	outf.close();
}

int ULC_BinFile::AppendSingleSerial(const char *fileName, ULC_Serial &serial)
{
	ofstream outf;
	ULC_BinFileHeader header;
	ULC_BinFileSerial binSerial;
	char *buf;

	if (fileName == NULL){
		return -1;
	}

	header.totalSerials = GetTotalSerials(fileName);

	outf.open(fileName, ios::out | ios::app | ios::binary);
	
	if (outf.fail()){
		return -1;
	}

	buf = (char *)&binSerial;
	SerialToBinSerial(&binSerial, &serial);
	outf.write(buf, sizeof(binSerial));

	outf.close();
	//Update header
	header.totalSerials++;
	header.latestSerial = serial.sn; 
	UpdateHeader(fileName, header);
	return 0;
}

int ULC_BinFile::AppendSerials(const char *fileName, int nSerials, ULC_Serial *serials)
{
	ofstream outf;
	ULC_BinFileHeader header;
	ULC_BinFileSerial binSerial;
	char *buf;

	if (fileName == NULL || nSerials <= 0 || serials == NULL){
		return -1;
	}

	header.totalSerials = GetTotalSerials(fileName);

	outf.open(fileName, ios::out | ios::app |  ios::binary);

	if (outf.fail()){
		return -1;
	}

	buf = (char *)&binSerial;

	for (int i = 0; i < nSerials; i++){
		SerialToBinSerial(&binSerial, serials + i);
		outf.write(buf, sizeof(binSerial));
	}

	outf.close();
	//Update header
	header.totalSerials += nSerials;
	header.latestSerial = serials[nSerials - 1].sn; 
	UpdateHeader(fileName, header);

	return 0;
}
